/**
 * 
 */
package be.gwi.alfresco.run.facade.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.dictionary.InvalidAspectException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;

import be.gwi.alfresco.run.exception.FacadeException;
import be.gwi.alfresco.run.facade.RunnerFacade;
import be.gwi.alfresco.run.facade.SearchTemplate;
import be.gwi.alfresco.run.model.RunModel;
import be.gwi.alfresco.run.model.Runner;

/**
 * 
 * @author gabriel.wilems@gmail.com
 * @see RunnerFacade
 * 
 * @version 1.0
 * @since 1.0
 */
public class RunnerFacadeImpl extends AbstractFacadeImpl implements RunnerFacade {
    private static Log    logger = LogFactory.getLog(RunnerFacadeImpl.class);

    private PersonService personService;
    private SearchService searchService;

    /**
     * Initializes the facade.
     * 
     * Verifies that all the attributes have been set.
     * 
     * @throws IllegalArgumentException
     *             if not all attributes have been set.
     */
    public void init() {
        super.init();
        Assert.notNull(this.personService, "The person service must have been set");
    }

    /*
     * (non-Javadoc)
     * 
     * @see be.gwi.alfresco.run.facade.RunnerFacade#getRunner(java.lang.String)
     */
    @Override
    public Runner getRunner(String username) throws FacadeException {
        NodeRef runnerNodeRef = getRunnerNodeRef(username);
        return this.getRunner(runnerNodeRef);
    }

    /*
     * (non-Javadoc)
     * 
     * @see be.gwi.alfresco.run.facade.RunnerFacade#isRunner(java.lang.String)
     */
    @Override
    public boolean isRunner(String username) throws FacadeException {
        NodeRef runnerNodeRef = getRunnerNodeRef(username);
        return isRunner(runnerNodeRef);
    }

    /*
     * (non-Javadoc)
     * 
     * @see be.gwi.alfresco.run.facade.RunnerFacade#initRunner(java.lang.String)
     */
    @Override
    public void initRunner(String username) throws FacadeException {
        NodeRef runnerNodeRef = getRunnerNodeRef(username);
        if (!isRunner(runnerNodeRef)) {
            this.nodeService.addAspect(runnerNodeRef, RunModel.ASPECT_RUNNER, null);
            Runner runner = new Runner();
            try {
                runner.setStartDate(new Date());
                updateRunner(runnerNodeRef, runner);
                ChildAssociationRef historyAssoc = this.nodeService.createNode(runnerNodeRef,
                        RunModel.ASSOC_RUNNER_HISTORY, RunModel.ASSOC_RUNNER_HISTORY, ContentModel.TYPE_FOLDER);
                NodeRef historyNodeRef = historyAssoc.getChildRef();
                this.nodeService.setProperty(historyNodeRef, ContentModel.PROP_NAME, "History");
                this.nodeService.setProperty(historyNodeRef, ContentModel.PROP_TITLE, "History of " + username);
            } catch (InvalidNodeRefException e) {
                throw new FacadeException("Unable to update runner", e);
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * be.gwi.alfresco.run.facade.RunnerFacade#updateRunner(be.gwi.alfresco.
     * run.model.Runner)
     */
    @Override
    public void updateRunner(Runner runner) {
        updateRunner(runner.getNodeRef(), runner);
    }

    /*
     * (non-Javadoc)
     * 
     * @see be.gwi.alfresco.run.facade.RunnerFacade#getRunners()
     */
    @Override
    public List<Runner> getRunners() throws FacadeException {
        String query = SearchTemplate.SEARCH_ALL_RUNNERS;
        try {
            List<NodeRef> runnerNodeRefList = this.searchUnlimitedNodeRefs(query, SearchService.LANGUAGE_LUCENE);

            List<Runner> runnerList = new ArrayList<Runner>();
            for (int i = 0; i < runnerNodeRefList.size(); i++) {
                runnerList.add(getRunner(runnerNodeRefList.get(i)));
            }
            return runnerList;
        } catch (Exception e) {
            throw new FacadeException("Unable to find all runners", e);
        }
    }

    /**
     * Searches for a nodeRefs list using the specified query in the specified
     * language.
     * <p>
     * This search is not limited by the MaxPermissionChecks nor by the
     * MaxPermissionCheckTimeMillis {@link SearchParameters} values.
     * <p>
     * Use this search if you want to get all nodeRefs without limitation.
     * 
     * @param query
     *            the search query.
     * 
     * @param language
     *            the search language.
     * 
     * @return the nodeRefs list returned by the search, {@code null} if no
     *         nodeRef have been found.
     * 
     * @see SearchParameters#setMaxPermissionChecks(int)
     * @see SearchParameters#setMaxPermissionCheckTimeMillis(long)
     */
    private List<NodeRef> searchUnlimitedNodeRefs(String query, String language) {
        SearchParameters sp = new SearchParameters();
        StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
        sp.addStore(storeRef);
        sp.setLanguage(language);
        sp.setQuery(query);
        sp.setLimitBy(LimitBy.UNLIMITED);
        sp.setMaxPermissionChecks(Integer.MAX_VALUE);
        sp.setMaxPermissionCheckTimeMillis(Long.MAX_VALUE);

        List<NodeRef> nodeRefs = null;
        ResultSet results = null;
        try {
            results = this.searchService.query(sp);
            nodeRefs = results.getNodeRefs();
        } finally {
            if (results != null) {
                results.close();
            }
        }

        return nodeRefs;
    }

    /**
     * Update Runner from given nodeRef
     * 
     * @param runnerNodeRef
     * @param runner
     * @throws InvalidNodeRefException
     */
    private void updateRunner(NodeRef runnerNodeRef, Runner runner) throws InvalidNodeRefException {
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_START_DATE, runner.getStartDate());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_RUNNING_DISTANCE, runner.getRunningDistance());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_RUNNING_COUNT, runner.getRunningCount());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_CYCLING_DISTANCE, runner.getCyclingDistance());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_CYCLING_COUNT, runner.getCyclingCount());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_SWIMMING_DISTANCE,
                runner.getSwimmingDistance());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_SWIMMING_COUNT, runner.getSwimmingCount());
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_RUN_POINT, Math.round(runner.getRunPoint() * 100.0) / 100.0);
        this.nodeService.setProperty(runnerNodeRef, RunModel.PROP_RUNNER_RUN_COUNT, runner.getRunCount());
    }

    /**
     * Get the Runner NodeRef from the given userName
     * 
     * @param username
     * @return
     */
    private NodeRef getRunnerNodeRef(String username) throws FacadeException {
        if (!this.personService.personExists(username)) {
            String message = new StringBuilder("User with username ").append(username).append(" does not exist")
                    .toString();
            logger.error(message);
            throw new FacadeException(message);
        }
        return this.personService.getPerson(username);
    }

    /**
     * Returns a runner with all its informations
     * 
     * @param runnerNodeRef
     *            the runner nodeRef.
     * 
     * @return a runner with all its informations.
     */
    private Runner getRunner(NodeRef runnerNodeRef) throws FacadeException {
        Runner runner = new Runner();
        if (isRunner(runnerNodeRef)) {
            Map<QName, Serializable> props = null;
            try {
                props = this.nodeService.getProperties(runnerNodeRef);
            } catch (Exception e) {
                String message = new StringBuilder("An error occured while getting runner ").append(runnerNodeRef)
                        .toString();
                throw new FacadeException(message, e);
            }
            runner.setNodeRef(runnerNodeRef);
            runner.setUsername((String)props.get(ContentModel.PROP_USERNAME));
            runner.setName((String)props.get(ContentModel.PROP_LASTNAME));
            runner.setFirstName((String)props.get(ContentModel.PROP_FIRSTNAME));
            runner.setStartDate((Date) props.get(RunModel.PROP_RUNNER_START_DATE));
            runner.setRunningDistance((Double) props.get(RunModel.PROP_RUNNER_RUNNING_DISTANCE));
            runner.setRunningCount((Integer) props.get(RunModel.PROP_RUNNER_RUNNING_COUNT));
            runner.setCyclingDistance((Double) props.get(RunModel.PROP_RUNNER_CYCLING_DISTANCE));
            runner.setCyclingCount((Integer) props.get(RunModel.PROP_RUNNER_CYCLING_COUNT));
            runner.setSwimmingDistance((Double) props.get(RunModel.PROP_RUNNER_SWIMMING_DISTANCE));
            runner.setSwimmingCount((Integer) props.get(RunModel.PROP_RUNNER_SWIMMING_COUNT));
            runner.setRunPoint((Double) props.get(RunModel.PROP_RUNNER_RUN_POINT));
            runner.setRunCount((Integer) props.get(RunModel.PROP_RUNNER_RUN_COUNT));
            List<ChildAssociationRef> historyAssoc = this.nodeService.getChildAssocs(runnerNodeRef,
                    RunModel.ASSOC_RUNNER_HISTORY, RegexQNamePattern.MATCH_ALL);
            if (historyAssoc != null && !historyAssoc.isEmpty()) {
                runner.setHistoryNodeRef(historyAssoc.get(0).getChildRef());
            }
        }
        return runner;
    }

    /**
     * Check if the given nodeRef is a runner
     * 
     * @param runnerNodeRef
     * @return
     * @throws InvalidNodeRefException
     * @throws InvalidAspectException
     */
    private boolean isRunner(NodeRef runnerNodeRef) throws InvalidNodeRefException, InvalidAspectException {
        return nodeService.hasAspect(runnerNodeRef, RunModel.ASPECT_RUNNER);
    }

    /**
     * @param personService
     *            the personService to set
     */
    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    /**
     * @param searchService
     *            the searchService to set
     */
    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

}
